﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;

namespace Boggle
{
	/// <summary>
	/// Finds dictionary types in a specified assembly and creates instances
	/// of those types.
	/// </summary>
	public class DictionaryEnumerator
	{
		/// <summary>
		/// The assembly to get dictionaries from.
		/// </summary>
		private Assembly assembly;

		/// <summary>
		/// Contains failures that occured during the last GetDictionaries operation.
		/// </summary>
		private IList<String> failureMessages;

		/// <summary>
		/// Gets the dictionary load failures that occured when last getting
		/// dictionaries.
		/// </summary>
		public ReadOnlyCollection<String> FailureMessages
		{
			get { return new ReadOnlyCollection<String>(failureMessages); }
		}

		/// <summary>
		/// Creates a new DictionaryEnumerator for the specified assembly.
		/// </summary>
		/// <param name="assembly"></param>
		public DictionaryEnumerator(Assembly assembly)
		{
			if (assembly == null)
			{
				throw new ArgumentNullException("assembly");
			}

			this.assembly = assembly;
			this.failureMessages = new List<String>();
		}

		/// <summary>
		/// Gets all dictionaries from the assembly specified in the constructor.
		/// </summary>
		/// <returns></returns>
		public IEnumerable<IBoggleDictionary> GetDictionaries()
		{
			failureMessages.Clear();

			return assembly.GetTypes()
				           .Where(t => t.GetInterface(typeof(IBoggleDictionary).ToString()) != null)
				           .Select(TryCreateDictionary)
				           .Where(d => d != null);
		}

		/// <summary>
		/// Attempts to create a dictionary instance from the supplied type.
		/// </summary>
		/// <param name="dictionaryType">The type of dictionary to create.</param>
		/// <returns>An instance of the dictionary if one could be created, otherwise
		/// null. If null is returned, a message is added to failureMessages.</returns>
		private IBoggleDictionary TryCreateDictionary(Type dictionaryType)
		{
			if(dictionaryType == null)
			{
				failureMessages.Add("Dictionary type was null");
				return null;
			}
			
			ConstructorInfo ctor;

			try
			{
				ctor = dictionaryType.GetConstructor(Type.EmptyTypes);

				if (ctor == null)
				{
					throw new ArgumentException("The dictionaryType specified has no default constructor");
				}
			}
			catch (Exception ex)
			{
				failureMessages.Add(String.Format("Unable to get parameterless constructor for dictionary type {0}: {1}", dictionaryType, ex));
				return null;
			}

			IBoggleDictionary dictionary;

			try
			{
				dictionary = (IBoggleDictionary)ctor.Invoke(null);
			}
			catch(Exception ex)
			{
				failureMessages.Add(String.Format("Unable to create instance of dictionary type {0} and convert it to {1}: {2}", 
					dictionaryType, typeof(IBoggleDictionary), ex));
				return null;
			}

			return dictionary;
		}

		/// <summary>
		/// Loads all dictionary types found in assemblies in the specified directory.
		/// </summary>
		/// <param name="directory">The directory to search in.</param>
		/// <returns>A sequence of IBoggleDictionaries.</returns>
		public static IEnumerable<IBoggleDictionary> LoadDictionariesFromDirectory(String directory)
		{
			if (directory == null)
			{
				throw new ArgumentNullException("directory");
			}

			return Directory.GetFiles(directory, "*.dll")
					        .Select(file => Assembly.LoadFrom(file))
					        .Select(assembly => new DictionaryEnumerator(assembly))
					        .SelectMany(enumerator => enumerator.GetDictionaries());
		}
	}
}
